
/* GCSx
** CLIPBOARD.CPP
**
** Our internal clipboard, with cross-platform clipboard reading/writing
** added transparently as code becomes available
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "SDL_syswm.h"
#include "all.h"

// Current internal clipboard
ClipboardType clipType = CLIPBOARD_NONE;
string* clipString = NULL;
SDL_Surface* clipImage = NULL;
Uint8* clipColors = NULL;
int clipColorCount = 0;
// (tile layer clipboard)
int clipLayerCount = 0;
int clipLayerWidth = 0;
int clipLayerHeight = 0;
Uint32** clipLayerData = NULL;
Uint32** clipLayerExt = NULL;
Uint32** clipLayerFx = NULL;
const void** clipLayerTileSet = NULL;
// (spawn clipboard)
int clipSpawnCount = 0;
int clipSpawnCountLayers = 0;
SpawnEdit** clipSpawnData = NULL;
int* clipSpawnLayers = NULL;


ClipboardType typeInClipboard() { start_func
    return clipType;
}

int canConvertClipboard(ClipboardType type) { start_func
    // Text type
#ifdef WIN32
    // WINDOWS CLIPBOARD
    if ((type == CLIPBOARD_TEXT_LINE) || (type == CLIPBOARD_TEXT_MULTI)) {
        if ((IsClipboardFormatAvailable(CF_TEXT)) || (IsClipboardFormatAvailable(CF_OEMTEXT))) return 1;
        return 0;
    }
#else
    if ((type == CLIPBOARD_TEXT_LINE) && (clipType == CLIPBOARD_TEXT_MULTI)) return 1;
    if ((type == CLIPBOARD_TEXT_MULTI) && (clipType == CLIPBOARD_TEXT_LINE)) return 1;
#endif
    // Other types
    if (type == clipType) return 1;
    return 0;
}

void clipboardClear() { start_func
    clipType = CLIPBOARD_NONE;
    delete clipString;
    clipString = NULL;
    delete[] clipColors;
    clipColors = NULL;
    clipColorCount = 0;
    if (clipImage) {
        SDL_FreeSurface(clipImage);
        clipImage = NULL;
    }
    if (clipLayerCount) {
        for (int pos = 0; pos < clipLayerCount; ++pos) {
            delete[] clipLayerData[pos];
            delete[] clipLayerExt[pos];
            delete[] clipLayerFx[pos];
        }
        delete[] clipLayerData;
        delete[] clipLayerExt;
        delete[] clipLayerFx;
        delete[] clipLayerTileSet;
        clipLayerData = NULL;
        clipLayerExt = NULL;
        clipLayerFx = NULL;
        clipLayerTileSet = NULL;
        clipLayerCount = 0;
    }
    if (clipSpawnCount) {
        for (int pos = 0; pos < clipSpawnCount; ++pos) {
            delete clipSpawnData[pos];
        }
        delete[] clipSpawnData;
        delete[] clipSpawnLayers;
        clipSpawnData = NULL;
        clipSpawnLayers = NULL;
        clipSpawnCount = 0;
        clipSpawnCountLayers = 0;
    }
}

void clipboardClearTileset(const void* tileset) { start_func
    if (clipType == CLIPBOARD_LAYER) {
        for (int pos = 0; pos < clipLayerCount; ++pos) {
            if (clipLayerTileSet[pos] == tileset) clipLayerTileSet[pos] = NULL;
        }
    }
    // @TODO: Spawns
}

void clipboardCopy(const string& text) { start_func
    // (clear any other types)
    clipboardClear();

#ifdef WIN32
    // WINDOWS CLIPBOARD
    SDL_SysWMinfo wmInfo;
    SDL_VERSION(&wmInfo.version);
    SDL_GetWMInfo(&wmInfo);
    if (OpenClipboard(wmInfo.window)) {
        // Add \r before every \n
        string data = text;
        string::size_type pos = 0;
        while ((pos = data.find('\n', pos)) != string::npos) {
            data.insert(pos, 1, '\r');
            pos += 2;
        }
    
        EmptyClipboard();
        HGLOBAL hClipboardData;
        hClipboardData = GlobalAlloc(GMEM_DDESHARE, data.size() + 1);
        char* pchData = (char*)GlobalLock(hClipboardData);
        strcpy(pchData, data.c_str());
        GlobalUnlock(hClipboardData);
        SetClipboardData(CF_TEXT, hClipboardData);
        CloseClipboard();
    }
#else
    // Store string
    delete clipString;
    clipString = NULL;
    clipString = new string(text);
    
    // Multiline?
    if (clipString->find_first_of('\n', 0) == string::npos) clipType = CLIPBOARD_TEXT_MULTI;
    else clipType = CLIPBOARD_TEXT_LINE;
#endif
}

void clipboardCopy(SDL_Surface* surface, int x, int y, int width, int height) { start_func
    assert(surface);
    assert(width > 0);
    assert(height > 0);

    // (clear any other types)
    clipboardClear();

    // Attempt to allocate surface
    clipImage = createSurface32(width, height);
    
    // Blit
    blit(x, y, surface, 0, 0, clipImage, width, height);
    clipType = CLIPBOARD_IMAGE;
}

void clipboardCopy(const Uint8* colors, int count) { start_func
    assert(count);
    assert(colors);
    
    // (clear any other types)
    clipboardClear();

    // Attempt to allocate memory
    clipColors = new Uint8[count * 4];
    
    // Copy
    memcpy(clipColors, colors, count * 4);
    clipType = CLIPBOARD_RGBA;
    clipColorCount = count;
}

void clipboardCopy(const vector<SpawnEdit const*>& spawns, const vector<int>& layers) { start_func
    assert(!spawns.empty());
    assert(!layers.empty());
    assert(spawns.size() == layers.size());
    
    // (clear any other types)
    clipboardClear();
    
    // Prepare memory
    clipSpawnCount = spawns.size();
    clipSpawnData = new SpawnEdit*[clipSpawnCount];
    clipSpawnLayers = new int[clipSpawnCount];
    
    // Loop through twice- first to determine range of layers and x/y positions
    int baseX = 0;
    int baseY = 0;
    set<int> layersUsed;
    vector<SpawnEdit const*>::const_iterator sPos = spawns.begin();
    vector<SpawnEdit const*>::const_iterator sEnd = spawns.end();
    vector<int>::const_iterator lPos = layers.begin();
    vector<int>::const_iterator lEnd = layers.end();
    for (; sPos != sEnd; ++sPos, ++lPos) {
        if (sPos == spawns.begin()) {
            baseX = (*sPos)->getX();
            baseY = (*sPos)->getY();
        }
        else {
            if ((*sPos)->getX() < baseX) baseX = (*sPos)->getX();
            if ((*sPos)->getY() < baseY) baseY = (*sPos)->getY();
        }
        layersUsed.insert(*lPos);
    }
    
    // Prepare for normalization of layer numbers to be zero-based
    map<int, int> layerMap;
    int layerNormalized = 0;
    for (set<int>::iterator pos = layersUsed.begin(); pos != layersUsed.end(); ++pos) {
        layerMap[*pos] = layerNormalized++;
    }
    clipSpawnCountLayers = layerNormalized;
    
    // Second loop, to copy to clipboard as normalized data
    sPos = spawns.begin();
    lPos = layers.begin();
    for (int pos = 0; sPos != sEnd; ++pos, ++sPos, ++lPos) {
        SpawnEdit* newSpawn = new SpawnEdit(*sPos, NULL);
        // Normalize position
        newSpawn->setPos(newSpawn->getX() - baseX, newSpawn->getY() - baseY, 1);
        clipSpawnData[pos] = newSpawn;
        // Normalize layer
        clipSpawnLayers[pos] = layerMap[*lPos];
    }
    
    clipType = CLIPBOARD_SPAWN;
}

void clipboardCopy(int numLayers, const void* const* tileset, const Uint32* const* data, const Uint32* const* dataExt, const Uint32* const* dataFx, int x, int y, int width, int height, int pitch) { start_func
    assert(numLayers);
    assert(data);
    assert(width);
    assert(height);
    assert(pitch);
    
    // (clear any other types)
    clipboardClear();

    // Attempt to allocate arrays
    clipLayerData = new Uint32*[numLayers];
    clipLayerExt = new Uint32*[numLayers];
    clipLayerFx = new Uint32*[numLayers];
    clipLayerTileSet = new const void*[numLayers];
    
    // Fill arrays with NULL
    for (int pos = 0; pos < numLayers; ++pos) {
        assert(data[pos]);
        clipLayerData[pos] = NULL;
        clipLayerExt[pos] = NULL;
        clipLayerFx[pos] = NULL;
        clipLayerTileSet[pos] = NULL;
    }
    
    // Copy each layer
    for (int pos = 0; pos < numLayers; ++pos) {
        // Tileset
        if (tileset) clipLayerTileSet[pos] = tileset[pos];
    
        // Allocate data space and copy
        clipLayerData[pos] = new Uint32[width * height];
        matrixCopy(data[pos] + x + y * pitch, clipLayerData[pos], width * 4, height, pitch * 4, width * 4);

        // If applicable, allocate ext space and copy
        if ((dataExt) && (dataExt[pos])) {
            clipLayerExt[pos] = new Uint32[width * height];
            matrixCopy(dataExt[pos] + x + y * pitch, clipLayerExt[pos], width * 4, height, pitch * 4, width * 4);
        }

        // If applicable, allocate fx space and copy
        if ((dataFx) && (dataFx[pos])) {
            clipLayerFx[pos] = new Uint32[width * height];
            matrixCopy(dataFx[pos] + x + y * pitch, clipLayerFx[pos], width * 4, height, pitch * 4, width * 4);
        }
    }
    
    // Store layer stats
    clipLayerCount = numLayers;
    clipLayerWidth = width;
    clipLayerHeight = height;
    clipType = CLIPBOARD_LAYER;
}

void clipboardPasteTextLine(string& paste) { start_func
#ifdef WIN32
    // WINDOWS CLIPBOARD
    SDL_SysWMinfo wmInfo;
    SDL_VERSION(&wmInfo.version);
    SDL_GetWMInfo(&wmInfo);
    if (OpenClipboard(wmInfo.window)) {
        if ((IsClipboardFormatAvailable(CF_TEXT)) || (IsClipboardFormatAvailable(CF_OEMTEXT))) {
            HANDLE hClipboardData = GetClipboardData(CF_TEXT);
            char *pchData = (char*)GlobalLock(hClipboardData);
            string data = pchData;
            GlobalUnlock(hClipboardData);
            CloseClipboard();
            // (cut at first \n or \r)
            string::size_type pos = data.find_first_of("\r\n", 0);
            if (pos == string::npos) paste = data;
            else paste = data.substr(0, pos);
        }
        else paste = blankString;
    }
    else paste = blankString;
#else
    if (clipType == CLIPBOARD_TEXT_LINE) {
        assert(clipString);
        paste = *clipString;
    }
    else if (clipType == CLIPBOARD_TEXT_MULTI) {
        assert(clipString);
        // (cut at first \n)
        string::size_type pos = clipString->find_first_of('\n', 0);
        if (pos == string::npos) paste = *clipString;
        else paste = clipString->substr(0, pos);
    }
    else paste = blankString;
#endif
}

void clipboardPasteTextMulti(string& paste) { start_func
#ifdef WIN32
    // WINDOWS CLIPBOARD
    SDL_SysWMinfo wmInfo;
    SDL_VERSION(&wmInfo.version);
    SDL_GetWMInfo(&wmInfo);
    if (OpenClipboard(wmInfo.window)) {
        if ((IsClipboardFormatAvailable(CF_TEXT)) || (IsClipboardFormatAvailable(CF_OEMTEXT))) {
            HANDLE hClipboardData = GetClipboardData(CF_TEXT);
            char *pchData = (char*)GlobalLock(hClipboardData);
            string data = pchData;
            GlobalUnlock(hClipboardData);
            CloseClipboard();
            // (remove all \r)
            string::size_type pos = 0;
            while ((pos = data.find('\r', pos)) != string::npos) {
                data.erase(pos, 1);
            }
            paste = data;
        }
        else paste = blankString;
    }
    else paste = blankString;
#else
    if ((clipType == CLIPBOARD_TEXT_LINE) || (clipType == CLIPBOARD_TEXT_MULTI)) {
        assert(clipString);
        paste = *clipString;
    }
    else paste = blankString;
#endif
}

void clipboardPasteImageSize(int* width, int* height) { start_func
    assert(width);
    assert(height);
    
    if (clipType == CLIPBOARD_IMAGE) {
        assert(clipImage);
        
        *width = clipImage->w;
        *height = clipImage->h;
    }
    else {
        *width = 0;
        *height = 0;
    }
}

void clipboardPasteImage(SDL_Surface* dest, int x, int y) { start_func
    assert(dest);
    
    if (clipType == CLIPBOARD_IMAGE) {
        blit(0, 0, clipImage, x, y, dest, clipImage->w, clipImage->h);
    }
}

int clipboardPasteColor(Uint8* dest, int count) { start_func
    assert(dest);
    
    if (clipType == CLIPBOARD_RGBA) {
        if (count > clipColorCount) count = clipColorCount;
        memcpy(dest, clipColors, count * 4);
        return count;
    }
    return 0;
}

void clipboardPasteLayerInfo(int* numLayers, int* width, int* height) { start_func
    assert(numLayers);
    assert(width);
    assert(height);
    
    if (clipType == CLIPBOARD_LAYER) {
        *numLayers = clipLayerCount;
        *width = clipLayerWidth;
        *height = clipLayerHeight;
    }
    else {
        *numLayers = 0;
        *width = 0;
        *height = 0;
    }
}

void clipboardPasteLayerInfoDetails(int layer, const void** tileset, int* hasExt, int* hasFx) { start_func
    assert(tileset);
    assert(hasFx);
    
    if (clipType == CLIPBOARD_LAYER) {
        assert(layer >= 0);
        assert(layer < clipLayerCount);
        
        *tileset = clipLayerTileSet[layer];
        *hasExt = clipLayerExt[layer] ? 1 : 0;
        *hasFx = clipLayerFx[layer] ? 1 : 0;
    }
    else {
        *tileset = NULL;
        *hasExt = 0;
        *hasFx = 0;
    }
}

void clipboardPasteLayer(int layer, Uint32* data, Uint32* dataExt, Uint32* dataFx, int x, int y, int width, int height, int pitch, Uint32 defaultData, Uint32 defaultExt, Uint32 defaultFx) { start_func
    assert(data);
    assert(pitch);
    assert(width);
    assert(height);
    
    if (clipType == CLIPBOARD_LAYER) {
        assert(layer >= 0);
        assert(layer < clipLayerCount);
        
        // First, main data
        matrixCopy(clipLayerData[layer], data + x + y * pitch, min(width, clipLayerWidth) * 4, min(height, clipLayerHeight), clipLayerWidth * 4, pitch * 4);

        // Next, ext?
        if (dataExt) {
            // Copy or fill?
            if (clipLayerExt[layer]) {
                matrixCopy(clipLayerExt[layer], dataExt + x + y * pitch, min(width, clipLayerWidth) * 4, min(height, clipLayerHeight), clipLayerWidth * 4, pitch * 4);
            }
            else {
                for (int row = min(height, clipLayerHeight); row > 0; --row) {
                    memSet32(dataExt + x + (y + row) * pitch, defaultExt, min(width, clipLayerWidth));
                }
            }
        }

        // Next, fx?
        if (dataFx) {
            // Copy or fill?
            if (clipLayerFx[layer]) {
                matrixCopy(clipLayerFx[layer], dataFx + x + y * pitch, min(width, clipLayerWidth) * 4, min(height, clipLayerHeight), clipLayerWidth * 4, pitch * 4);
            }
            else {
                for (int row = min(height, clipLayerHeight); row > 0; --row) {
                    memSet32(dataFx + x + (y + row) * pitch, defaultFx, min(width, clipLayerWidth));
                }
            }
        }
    }
}

void clipboardPasteSpawnInfo(int* numSpawns, int* numLayers) { start_func
    assert(numSpawns);
    assert(numLayers);
    
    if (clipType == CLIPBOARD_SPAWN) {
        *numSpawns = clipSpawnCount;
        *numLayers = clipSpawnCountLayers;
    }
    else {
        *numSpawns = 0;
        *numLayers = 0;
    }
}

void clipboardPasteSpawn(int spawnNum, SpawnEdit* newSpawn, int* layerNum, int baseX, int baseY) { start_func
    assert(clipType == CLIPBOARD_SPAWN);
    assert(newSpawn);
    assert(layerNum);
    assert(spawnNum >= 0);
    assert(spawnNum < clipSpawnCount);
    
    *layerNum = clipSpawnLayers[spawnNum];
    newSpawn->setPos(clipSpawnData[spawnNum]->getX() + baseX, clipSpawnData[spawnNum]->getY() + baseY);
    newSpawn->setName(clipSpawnData[spawnNum]->getName());
    newSpawn->setScript(clipSpawnData[spawnNum]->getScript());
    newSpawn->setSprite(clipSpawnData[spawnNum]->getAnimgroup(), clipSpawnData[spawnNum]->getTileset(),
                        clipSpawnData[spawnNum]->getSubid());
}
